package gmmxEditor.GUI;

import gmmxEditor.ISimpleDrawable;
import gmmxEditor.Preferences;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import javax.swing.JPanel;

import mh.entity.EnumState;

public class TilesPane<E extends ISimpleDrawable> extends JPanel {
	private static final long	serialVersionUID	= -3079837565399511467L;

	private Map<Rectangle, E>	objectsPositions;

	private Point				pointer;

	/**
	 * Border between objects expresses in pixels
	 */
	private int					objectBorder		= 2;

	public TilesPane() {
		super();
		this.objectsPositions = new HashMap<Rectangle, E>();
		this.pointer = null;
	}

	/**
	 * Returns the object at the specified position
	 * 
	 * @param int x
	 * @param int y
	 * @return E
	 */
	public E getAt(int x, int y) {
		return this.getAt(new Point(x, y));
	}

	/**
	 * Returns the object at the specified position
	 * 
	 * @param Point
	 *            p
	 * @return E
	 */
	public E getAt(Point p) {

		Iterator<Entry<Rectangle, E>> it = this.objectsPositions.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Rectangle, E> pairs = it.next();

			if ((pairs != null) && pairs.getKey().contains(p)) {
				return pairs.getValue();
			}
		}

		return null;
	}

	public Rectangle getRect(Point p) {
		Iterator<Entry<Rectangle, E>> it = this.objectsPositions.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Rectangle, E> pairs = it.next();

			if ((pairs != null) && pairs.getKey().contains(p)) {
				return pairs.getKey();
			}
		}

		return null;
	}

	private E getElementByPoint(Point p) {
		if (p == null) {
			return null;
		}

		Iterator<Entry<Rectangle, E>> it = this.objectsPositions.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Rectangle, E> pairs = it.next();

			if ((pairs != null) && pairs.getKey().contains(p)) {
				return pairs.getValue();
			}
		}

		return null;
	}

	/**
	 * Adds the object to the panel
	 * 
	 * @param IDrawable
	 *            obj
	 */
	public void addObject(E obj) {
		Vector<Rectangle> clears = this.getClearSquares();

		//		System.out.println("Trying to insert object " + obj.getEntityDatabaseID() + " (" + obj.getSize().getWidth() + "," + obj.getSize().getHeight() + ")");

		for (Rectangle clear : clears) {
			//			System.out.println(clear);
			if ((clear.getHeight() >= (obj.getSize().getHeight() + this.objectBorder)) && (clear.getWidth() >= (obj.getSize().getWidth() + this.objectBorder))) {
				this.objectsPositions.put(new Rectangle((int) clear.getX(), (int) clear.getY(), obj.getSize().getWidth() + this.objectBorder, obj.getSize()
						.getHeight() + this.objectBorder), obj);
				//				System.out.println("Inserted in position: " + clear.getX() + ", " + clear.getY() + ", " + obj.getSize().getWidth() + ", " + obj.getSize().getHeight());
				break;
			}
		}
	}

	/**
	 * Adds multiple objects to the panel
	 * 
	 * @param IDrawable
	 *            obj
	 */
	public void addObjects(Vector<E> objs) {
		objs = this.sortByEntityDatabaseID(objs);

		for (E obj : objs) {
			if (!this.contains(obj)) {
				this.addObject(obj);
			} else {
				//TODO replace with logger info
				System.out.println("Object with id " + obj.getEntityDatabaseID() + " already in set");
			}
		}
	}

	/**
	 * Draws the object
	 */
	@Override
	protected void paintComponent(Graphics g) {
		Iterator<Entry<Rectangle, E>> it = this.objectsPositions.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<Rectangle, E> pairs = it.next();

			pairs.getValue().draw(g, EnumState.INTACT, (int) pairs.getKey().getX(), (int) pairs.getKey().getY());
		}

		if (this.pointer != null) {
			Rectangle rect = this.getRect(this.pointer);

			g.setColor(Preferences.getInstance().getSquareColor());

			//TODO add management of objects selector square color

			if (rect != null) {
				g.fillRect((int) rect.getX(), (int) rect.getY(), (int) rect.getWidth() - this.objectBorder, (int) rect.getHeight() - this.objectBorder);
			}
		}

	}

	/**
	 * Sets the pointer to the correct object
	 * 
	 * @param p
	 */
	public void setPointer(Point p) {
		this.pointer = p;
	}

	public E getSelectedObject() {
		return this.getElementByPoint(this.pointer);
	}

	public int elements() {
		return this.objectsPositions.size();
	}

	private boolean contains(E obj) {
		for (Entry<Rectangle, E> entry : this.objectsPositions.entrySet()) {
			if (entry.getValue().getEntityDatabaseID() == obj.getEntityDatabaseID()) {
				return true;
			}
		}
		return false;
	}

	private boolean isPointClear(int x, int y) {
		for (Entry<Rectangle, E> rect : this.objectsPositions.entrySet()) {
			if (rect.getKey().contains(x, y)) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns an array containing all the clear squares found in the object
	 * 
	 * @return
	 */
	private Vector<Rectangle> getClearSquares() {
		Vector<Rectangle> ret = new Vector<Rectangle>();

		int width = this.getWidth();
		int height = this.getHeight();

		for (int x = 0; x < width; x++) {
			for (int y = 0; (y < height) && (x < width); y++) {
				if (this.isPointClear(x, y)) {
					int xMax = x + 1;
					while ((xMax < width) && this.isPointClear(xMax, y) && ((y == 0) || !this.isPointClear(xMax, y - 1))) {
						xMax++;
					}
					ret.add(new Rectangle(x, y, xMax - x, height - y));
					y = -1;
					x = xMax;
				}
			}
		}
		//TODO add inverted control (check for lines instead of columns)
		return ret;
	}

	/**
	 * Sorts the vector by entityDatabaseID
	 * 
	 * @param unsortedMap
	 * @return Vector<E>
	 */
	private Vector<E> sortByEntityDatabaseID(Vector<E> unsortedMap) {

		Vector<E> ret = new Vector<E>();

		Collections.sort(unsortedMap, new Comparator<E>() {
			@Override
			public int compare(E o1, E o2) {
				return new Integer(o1.getEntityDatabaseID()).compareTo(new Integer(o1.getEntityDatabaseID()));
			}
		});

		for (E e : unsortedMap) {
			ret.add(e);
		}

		return ret;
	}

	/**
	 * Returns the requested object selected by entityDatabaseID
	 * 
	 * @param id
	 *            int
	 * @return E
	 */
	public E getByID(int id) {
		for (Entry<Rectangle, E> e : this.objectsPositions.entrySet()) {
			if (e.getValue().getEntityDatabaseID() == id) {
				return e.getValue();
			}
		}

		return null;
	}

}
